﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using CaptchaLearning.Infrastructure;
using CaptchaLearning.Infrastructure.Model;

namespace CaptchaLearning.Trainning
{
    public class TrainningViewModel : WorkspaceViewModel
    {
        #region Fields and Properties

        private ObservableCollection<string> _characters;
        public ObservableCollection<string> Characters
        {
            get { return _characters; }
            set { _characters = value; OnPropertyChanged("Characters"); }
        }

        private Character _currentCharacter;
        private System.Drawing.Bitmap _currentBitmap;

        private string _character;
        public string Character
        {
            get { return _character; }
            set
            {
                _character = value;
                if (File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "Image", _character + ".bmp")))
                {
                    BitmapImage tmp = new BitmapImage();
                    tmp.BeginInit();
                    tmp.CreateOptions = BitmapCreateOptions.None;
                    tmp.CacheOption = BitmapCacheOption.OnLoad;
                    tmp.StreamSource = new FileStream(Path.Combine(Directory.GetCurrentDirectory(), "Image", _character + ".bmp"),
                                                FileMode.Open, FileAccess.Read);
                    tmp.EndInit();
                    Image = tmp;
                }
                else
                    Image = null;
                OnPropertyChanged("Character");
            }
        }

        private string _lblMatrix;
        public string LblMatrix
        {
            get { return _lblMatrix; }
            set
            {
                _lblMatrix = value;
                OnPropertyChanged("LblMatrix");
            }
        }

        private BitmapImage _image;
        public BitmapImage Image
        {
            get { return _image; }
            set { _image = value; OnPropertyChanged("Image"); }
        }

        private string _matrix;
        public string Matrix
        {
            get { return _matrix; }
            set
            {
                _matrix = value;
                OnPropertyChanged("Matrix");
            }
        }

        private bool _transitionMatrixChecked;
        public bool TransitionMatrixChecked
        {
            get { return _transitionMatrixChecked; }
            set
            {
                _transitionMatrixChecked = value;
                if (_transitionMatrixChecked)
                {
                    LoadTransitionMatrix(_currentCharacter);
                    LblMatrix = "Transition Matrix";
                }
                OnPropertyChanged("TransitionMatrixChecked");
            }
        }

        private bool _emissionMatrixChecked;
        public bool EmissionMatrixChecked
        {
            get { return _emissionMatrixChecked; }
            set
            {
                _emissionMatrixChecked = value;
                if (_emissionMatrixChecked)
                {
                    LoadEmissionMatrix(_currentCharacter);
                    LblMatrix = "Emission Matrix";
                }
                OnPropertyChanged("EmissionMatrixChecked");
            }
        }

        private bool _totalMatrixChecked;
        public bool TotalMatrixChecked
        {
            get { return _totalMatrixChecked; }
            set
            {
                _totalMatrixChecked = value;
                if (_totalMatrixChecked)
                {
                    LoadTotalMatrix(_currentCharacter);
                    LblMatrix = "Total Matrix";
                }
                OnPropertyChanged("TotalMatrixChecked");
            }
        }

        #endregion // Fields and Properties

        public TrainningViewModel()
        {
            this.DisplayName = "Trainning";
            this._characters = Global.Characters;
            this._image = new BitmapImage();

            this._learnCommand = new DelegateCommand(new Action(ExecuteLearnCommand), () => true);
            this._paintCommand = new DelegateCommand(new Action(ExecutePaintCommand), () => true);
            this._clearCommand = new DelegateCommand(new Action(ExecuteClearCommand), () => true);
        }

        #region Commands

        /// <summary>
        /// Thực hiện thao tác training HMM
        /// </summary>
        private ICommand _learnCommand;
        public ICommand LearnCommand
        {
            get { return _learnCommand; }
        }
        public void ExecuteLearnCommand()
        {
            if (string.IsNullOrEmpty(Character))
            {
                MessageBox.Show("You must select character first");
                return;
            }
            Infrastructure.Model.Character character = GetCharacterByName(Character, Global.CurrentProject.Characters);
            _currentCharacter = character;
            if (character != null)
            {
                if (Image == null)
                {
                    MessageBox.Show("You must draw it first");
                    return;
                }
                ObservableCollection<Infrastructure.Model.ImageColumn> thinImage = GetThinImage(Image);

                SetObservations(character, thinImage);
                SetStates(character, thinImage);
                SetPi(character);

                SetTransitionMatrix(character);
                SetEmissionMatrix(character);

                Global.CurrentProject.Algorithm.Execute(character);
            }
        }

        /// <summary>
        /// Thực hiện thao tác vẽ 1 ký tự, kết quả save file bmp xuống thư mục Image
        /// </summary>
        private ICommand _paintCommand;
        public ICommand PaintCommand
        { get { return _paintCommand; } }
        public void ExecutePaintCommand()
        {
            if (string.IsNullOrEmpty(Character))
            {
                MessageBox.Show("You must select character first");
                return;
            }
            Painter.Painter paint = new Painter.Painter();
            paint.Name = Character;
            paint.Image = Image;
            paint.ShowDialog();
            if (paint.Save)
            {
                BitmapImage tmp = new BitmapImage();
                tmp.BeginInit();
                tmp.CreateOptions = BitmapCreateOptions.None;
                tmp.CacheOption = BitmapCacheOption.OnLoad;
                tmp.StreamSource = new FileStream(Path.Combine(Directory.GetCurrentDirectory(), "Image", Character + ".bmp"),
                                            FileMode.Open, FileAccess.Read);
                tmp.EndInit();
                Image = tmp;
            }
        }

        /// <summary>
        /// Xóa hình của ký tự đó trong thư mục image
        /// </summary>
        private ICommand _clearCommand;
        public ICommand ClearCommand
        { get { return _clearCommand; } }
        public void ExecuteClearCommand()
        {
            if (string.IsNullOrEmpty(Character))
            {
                MessageBox.Show("You must select character first");
                return;
            }
            if (Image == null)
            {
                MessageBox.Show("Have no image to remove");
                return;
            }
            Image.StreamSource.Dispose();
            if (File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "Image", Character + ".bmp")))
                File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "Image", Character + ".bmp"));
            Image = null;
        }

        #endregion // Commands

        #region Helpers

        /// <summary>
        /// Trạng thái ẩn xuất hiện đầu tiên là trạng thái S1
        /// </summary>
        private void SetPi(Infrastructure.Model.Character character)
        {
            character.Pi = new ObservableCollection<double>();
            character.Pi.Add(1.0);
            for (int i = 1; i < character.States.Count; i++)
            {
                character.Pi.Add(0.0);
            }
        }

        /// <summary>
        /// Tìm model character theo tên của nó trong danh sách ký tự
        /// </summary>
        private Infrastructure.Model.Character GetCharacterByName(string name, ObservableCollection<Infrastructure.Model.Character> characters)
        {
            foreach (Infrastructure.Model.Character character in characters)
            {
                if (character.Name.Equals(name))
                    return character;
            }
            return null;
        }

        /// <summary>
        /// Scale tấm hình bitmap lại theo kích thước pixel rồi lấy danh sách thin image của nó
        /// </summary>
        private ObservableCollection<Infrastructure.Model.ImageColumn> GetThinImage(BitmapImage image)
        {
            ObservableCollection<Infrastructure.Model.ImageColumn> thinImages = new ObservableCollection<Infrastructure.Model.ImageColumn>();
            System.Drawing.Bitmap bitmap = ResizeImage(image, Global.CurrentProject.Pixels, Global.CurrentProject.Pixels);
            this._currentBitmap = bitmap;
            // TODO: get all column pixel in bitmap and add it into thinImages
            // ARGB value: Black->#FF000000, White->#FFFFFFFF
            // width: x, height: y

            Infrastructure.Model.ImageColumn tmpImageColumn;
            int _width = bitmap.Width, _height = bitmap.Height;
            System.Drawing.Color _color;
            for (int _w = 0; _w < _width; _w++)
            {
                tmpImageColumn = new Infrastructure.Model.ImageColumn();
                tmpImageColumn.Order = _w;

                for (int _h = 0; _h < _height; _h++)
                {
                    _color = bitmap.GetPixel(_w, _h);
                    if ((_color.R>=254) && (_color.G>=254) && (_color.B >= 254)) { tmpImageColumn.Pixels.Add(0); }
                    else { tmpImageColumn.Pixels.Add(1); }
                }
                thinImages.Add(tmpImageColumn);
            }
            return thinImages;
        }

        private void checkBitmap(System.Drawing.Bitmap bitmap)
        {
            int _width = bitmap.Width, _height = bitmap.Height;
            System.Drawing.Color _color;
            string test = "Bitmap:\n"; 
            bitmap.Save("TestBitmap.png");
            for (int _w = 0; _w < _width; _w++)
            {
                test += "C" + _w;
                for (int _h = 0; _h < _height; _h++)
                {
                    _color = bitmap.GetPixel(_w, _h);
                    test += "[" + _color.A + "," + _color.R + "," + _color.G + "," + _color.B + "] ";
                }
                test += "\n";
            }
            test += "\n";
            Matrix = test;
        }

        private void checkObser(Infrastructure.Model.Character character)
        {
            string text = "Cot Pixel:\n";
            for (int i = 0; i < character.Observations[0].Pixels.Count; i++)
            {
                for (int j = 0; j < character.Observations.Count; j++)
                {
                    text += "" + character.Observations[j].Pixels[i];
                }
                text += "\n";
            }
            text += "\n";
            Matrix += text;
        }

        /// <summary>
        /// Từ chuỗi thin image thì chuỗi trạng thái quan sát chính là nó
        /// </summary>
        /// 
        private void SetObservations(Infrastructure.Model.Character character,
                                     ObservableCollection<Infrastructure.Model.ImageColumn> imageColumns)
        {
            character.Observations = imageColumns;
        }

        /// <summary>
        /// Từ chuỗi thin image lọc ra chuỗi trạng thái quan sát
        /// DT: Add Col[0], for Col[i] <> Col[i+1] => add(Col[i+1])
        /// </summary>
        /// 
        private bool isSameColumn(Infrastructure.Model.ImageColumn c1, Infrastructure.Model.ImageColumn c2)
        {
            for (int i = 0; i < c1.Pixels.Count; i++)
            {
                if (c1.Pixels[i] != c2.Pixels[i])
                    return false;
            }
            return true;
        }

        private void SetStates(Infrastructure.Model.Character character,
                                     ObservableCollection<Infrastructure.Model.ImageColumn> imageColumns)
        {
            // duplicate imageColumns
            ObservableCollection<Infrastructure.Model.ImageColumn> expandImageColumns = new ObservableCollection<Infrastructure.Model.ImageColumn>();
            Infrastructure.Model.ImageColumn tmpImageColumn;

            //Insert one empty columns into Start
            //tmpImageColumn = new Infrastructure.Model.ImageColumn();
            //for (int i = 0; i < imageColumns[0].Pixels.Count; i++) tmpImageColumn.Pixels.Add(0);
            //expandImageColumns.Add(tmpImageColumn);

            for (int i = 0; i < imageColumns.Count; i++)
            {
                tmpImageColumn = new Infrastructure.Model.ImageColumn();
                tmpImageColumn.Pixels = imageColumns[i].Pixels;
                tmpImageColumn.Order = i;
                expandImageColumns.Add(tmpImageColumn);
            }

            //  traverse this list by couple and remove duplicate
            ObservableCollection<ImageColumn> tmp = new ObservableCollection<ImageColumn>();

            tmp.Add(expandImageColumns[0]);
            for (int i = 0; i < expandImageColumns.Count - 1; i++)
            {
                if (!isSameColumn(expandImageColumns[i], expandImageColumns[i + 1])) { tmp.Add(expandImageColumns[i + 1]); }
            }
            character.States = tmp;
            //For Test exist 2 Begin and End States
            //if (character.States.Count == 1) { character.States.Add(imageColumns[expandImageColumns.Count - 1]); }
            //For sort Order Image Columns
            //for (int i = 0; i < character.States.Count; i++) { character.States[i].Order = i; }
        }

        /// <summary>
        /// Transition matrix can be calculated by it's states list, just 50% to next and 50% still there
        /// </summary>
        private void SetTransitionMatrix(Infrastructure.Model.Character character)
        {
            // matrix S1 -> Sn .5 .5 .0
            //        ..       .0 .5 .5
            //        Sn -> Sn .0 .0  1
            character.TransitionMatrix.StateCount = character.States.Count;
            int nState = character.TransitionMatrix.StateCount;
            List<List<double>> tmp = new List<List<double>>();
            for (int i = 0; i < nState; i++)
            {
                List<double> subList = new List<double>();
                for (int j = 0; j < nState; j++)
                {
                    subList.Add((double)(1.0 / (nState - j)));
                }
                tmp.Add(subList);
            }
            character.TransitionMatrix.Probability = tmp;
            character.TransitionMatrix.Probability[nState - 1][nState - 1] = 1.0;
        }

        /// <summary>
        /// Emission matrix can be calculated by it's states list, just 50% to next and 50% still there
        /// </summary>
        /// 
        private bool isSubSet(Infrastructure.Model.ImageColumn s1, Infrastructure.Model.ImageColumn o1)
        {
            for (int i = 0; i < s1.Pixels.Count; i++)
            {
                if (s1.Pixels[i] == 1 && o1.Pixels[i] == 0)
                    return false;
            }
            return true;
        }

        private int countBlackPixel(Infrastructure.Model.ImageColumn s)
        {
            int nBlackPixel = 0;
            for (int i = 0; i < s.Pixels.Count; i++)
            {
                if (s.Pixels[i] == 1)
                    nBlackPixel++;
            }
            return nBlackPixel;
        }

        private int countSubSet(Infrastructure.Model.ImageColumn s1, Infrastructure.Model.Character character)
        {
            int count = 0;
            for (int i = 0; i < character.Observations.Count; i++)
            {
                if(isSubSet(s1, character.Observations[i]))
                    count++;
            }
            return count;
        }

        private void SetEmissionMatrix(Infrastructure.Model.Character character)
        {
            // matrix   o1 o2 o3 o4 o5 on
            //        s1
            //        s2
            //        sm
            int nState = character.TransitionMatrix.StateCount;
            int nObser = character.Observations.Count;

            int nSubSet;
            int nPixel = character.States[0].Pixels.Count;
            int nCasePixels = (int)Math.Pow(2, nPixel);
            int nBlackPixelState, nBlackPixelObser;

            double sumRow = 0.0;
            character.EmissionMatrix.ObservationCount = nObser;
            character.EmissionMatrix.StateCount = nState;

            List<List<double>> tmp = new List<List<double>>();
            for (int i = 0; i < nState; i++)
            {
                List<double> subList = new List<double>();
                nBlackPixelState = countBlackPixel(character.States[i]);
                nSubSet = countSubSet(character.States[i], character);
                sumRow = 0.0;
                for (int j = 0; j < nObser; j++)
                {
                    nBlackPixelObser = countBlackPixel(character.Observations[j]);
                    // Calculator probability
                    if (nBlackPixelState == 0)
                    {
                        if (nBlackPixelObser == 0)
                            subList.Add(1.0);
                        else subList.Add(0.0);
                    }
                    else if (nBlackPixelState == 1)
                    {
                        if (nBlackPixelObser == 1)
                            subList.Add(1.0);
                        else subList.Add(0.0);
                    }
                    else 
                    { 
                        if(isSubSet(character.States[i], character.Observations[j]))
                            subList.Add(1 / Math.Pow(2, nPixel - nBlackPixelState)); 
                        else 
                            subList.Add(0.0);
                    }
                }
                // Estimate lại cho tổng hàng bằng 1
                for (int j = 0; j < nObser; j++)
                {
                    sumRow += subList[j];
                }
                for (int j = 0; j < nObser; j++)
                {
                    subList[j] /= sumRow;
                }
                // Save
                tmp.Add(subList);
            }
            character.EmissionMatrix.Probability = tmp;
        }

        /// <summary>
        /// Scale image to width x height size
        /// </summary>
        public System.Drawing.Bitmap ResizeImage(BitmapImage image, int width, int height)
        {
            System.Drawing.Image source = System.Drawing.Image.FromStream(Image.StreamSource);

            System.Drawing.Bitmap result = new System.Drawing.Bitmap(width, height);

            result.SetResolution(source.HorizontalResolution, source.VerticalResolution);

            using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(result))
            {
                graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                graphics.DrawImage(source, 0, 0, result.Width, result.Height);
            }

            return result;
        }

        /// <summary>
        /// Load ma trận transition của character lên
        /// </summary>
        private void LoadTransitionMatrix(Infrastructure.Model.Character character)
        {
            if (character == null)
                return;
            //For Test Transition
            string text = string.Empty;
            for (int i = 0; i < character.TransitionMatrix.StateCount; i++)
            {
                text += string.Format("{0,5:##} || ", i);
                for (int j = 0; j < character.TransitionMatrix.StateCount; j++)
                {
                    text += string.Format("{0,-5:0.000} | ", character.TransitionMatrix.Probability[i][j]);
                }
                text += "|\n";
            }
            Matrix = text;

            
        }

        /// <summary>
        /// Load ma trận emission của character lên
        /// </summary>
        private void LoadEmissionMatrix(Infrastructure.Model.Character character)
        {
            if (character == null)
                return;
            string text = string.Empty;
            for (int i = 0; i < character.EmissionMatrix.StateCount; i++)
            {
                text += string.Format("{0,-5} || ", i);
                for (int j = 0; j < character.EmissionMatrix.ObservationCount; j++)
                {
                    text += string.Format("{0,-5:0.000} | ", character.EmissionMatrix.Probability[i][j]);
                }
                text += "|\n";
            }
            Matrix = text;
        }

        private void LoadTotalMatrix(Infrastructure.Model.Character character)
        {
            if (character == null)
                return;
            checkBitmap(_currentBitmap);
            checkObser(character);
        }

        #endregion // Helpers
    }
}